/*
 * Copyright (c) United Binary LLC.  All rights reserved.
 * 
 * This code is licensed under the MIT License
 * 
 * SEE: http://harnessit.codeplex.com/license
 * 
 */
using System;
using System.Diagnostics;
using System.IO;
using UnitedBinary.UnitTests.Framework;
using System.Collections.Generic;

namespace UnitedBinary.HarnessIt.SharedTypes
{
	/// <summary>
	///    The Assembly class represents an assembly which will likely contain unit tests.
	/// </summary>
	[Serializable]
	public class Assembly : IComparable, IAssembly
	{
		#region Member Variables

		private bool active = true;
		private string name;
		private string fullFilePath;
		private string description = "";
		private string version;
		private int totalTestMethodCount; // set this when pulling classes... sum Class.TestCount
		//private int activeTestCount;
		private int requiredTestCount;
		private int totalClassCount;
		private Class[] classes;
		private bool isOrdered;
		private int subOrderMode = 100;
		private TestOrderModes orderMode = TestOrderModes.Neutral;

		#endregion

		#region Construction

		/// <summary>
		///		Constructor reads an assembly (using reflection) and
		///		loads its classes into this class for operating on.
		/// </summary>
		/// <param name="filename">Assembly to load</param>
		public Assembly(string filename)
		{
//			// Set up the AppDomainSetup
//			AppDomainSetup setup = new AppDomainSetup();
//			setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
//			setup.ConfigurationFile = AppDomain.CurrentDomain.BaseDirectory + "HarnessIt.exe.config";
//
//			// Set up the Evidence
//			Evidence baseEvidence = AppDomain.CurrentDomain.Evidence;
//			Evidence evidence = new Evidence(baseEvidence);
//			evidence.AddAssembly(AppDomain.CurrentDomain.BaseDirectory + "HarnessIt.exe");
//
//			// Create the AppDomain      
//			AppDomain loaderDomain = AppDomain.CreateDomain("Loader AppDomain", evidence, setup);

			if (!Path.IsPathRooted(filename))
			{
				filename = Path.GetFullPath(filename);
			}
			System.Reflection.Assembly A = System.Reflection.Assembly.LoadFrom( filename );

			fullFilePath = filename;
			name = A.FullName.Substring( 0, A.FullName.IndexOf( "," ) );

			version = FileVersionInfo.GetVersionInfo( fullFilePath ).FileVersion;

			try
			{
				foreach ( Attribute attribute in Attribute.GetCustomAttributes( A, true ) )
				{
					TestAssemblyAttribute taa = attribute as TestAssemblyAttribute;
					if ( taa != null )
					{
						description = taa.Description;
						continue;
					}
					TestOrderAttribute toa = attribute as TestOrderAttribute;
					isOrdered = toa != null;
					if ( isOrdered )
					{
						orderMode = toa.Mode;
						subOrderMode = toa.SubModeOrder;
						continue;
					}
				}
			}
			catch ( FileLoadException e )
			{
				throw new InconsistentTestBasisException( "Inconsistent Test Basis Assembly.", e );
			}

			List<Class> classList = new List<Class>();

			Type[] types = A.GetTypes();
			foreach ( Type t in types )
			{
				if ( t.IsClass )
				{
					totalClassCount++;
					Class c = new Class( t, this );
					if ( c.IsTest )
					{
						totalTestMethodCount += c.TestCount;
						classList.Add( c );
					}
					c = null;
				}
			}
			classList.Sort();

			this.classes = classList.ToArray();
			A = null;

			//AppDomain.Unload(loaderDomain);
		}

		#endregion

		#region Version related methods.

		/// <summary>
		///		The version of the this assembly.
		/// </summary>
		public string Version
		{
			get { return version; }
		}

		#endregion

		#region Unit Test Related Code

		/// <summary>
		///		The full namespace name of the assembly.
		/// </summary>
		public string Name
		{
			get { return name; }
		}

		/// <summary>
		///		ContainsTests indicates whether any of the assembly's classes 
		///		contain test methods.
		/// </summary>
		public bool ContainsTests
		{
			get { return classes.Length > 0; }
		}

		/// <summary>
		///		ClassCount is the number of classes in this assembly.
		/// </summary>
		public int ClassCount
		{
			get { return totalClassCount; }
		}

		/// <summary>
		///		Classes is an array of "Class" objects representing the classes
		///		in this assembly.
		/// </summary>
		public Class[] Classes
		{
			get { return classes; }
		}

		/*
		internal int ActiveTestCount
		{
			get
			{
				return activeTestCount;
			}
			set
			{
				activeTestCount = value;
			}
		}
		*/

		internal int RequiredTestCount
		{
			get { return requiredTestCount; }
			set { requiredTestCount = value; }
		}

		public bool ContainsRequiredTests
		{
			get { return requiredTestCount > 0; }
		}

		public bool ContainsOnlyRequiredTests
		{
			get { return ContainsRequiredTests && (requiredTestCount == totalTestMethodCount); }
		}

		/// <summary>
		///		Active indicates whether the tests in this assembly will be run.
		/// </summary>
		public bool Active
		{
			get { return active; }
			set { active = value; }
		}


		/// <summary>
		///		ContainsActiveTests is similar to Active, but rather indicates whether
		///		any of the assembly's classes have test methods which are set to active.
		/// </summary>
		public bool ContainsActiveTests
		{
			get
			{
				foreach ( Class c in classes )
				{
					if ( c.ContainsActiveTests )
					{
						return true;
					}
				}
				return false;
			}
		}

		/// <summary>
		///		TestRecord is a TestMethodRecord which represents the cummulative 
		///		TestMethodRecord of all the classes in this assembly.
		/// </summary>
		public TestMethodRecord TestRecord
		{
			get
			{
				TestMethodRecord tmr = new TestMethodRecord();
				foreach ( Class c in classes )
				{
					tmr += c.TestRecord;
				}
				return tmr;
			}
		}

		/// <summary>
		///		ResetTests resets the test results for all the classes in this assembly.
		/// </summary>
		public void ResetTests()
		{
			foreach ( Class c in classes )
			{
				c.ResetTests();
			}
		}

		/// <summary>
		///		FullPathName is the full path name of this assembly (e.g. C:\My Dir\MyDll.dll).
		/// </summary>
		public string FullPathName
		{
			get { return fullFilePath; }
		}

		public double RunningTime
		{
			get
			{
				double dt = 0.0;
				foreach ( Class c in classes )
				{
					dt += c.RunningTime;
				}
				return dt;
			}
		}

		#endregion

		#region Test Attribute Properties

		public string Description
		{
			get { return description; }
		}

		public TestOrderModes OrderMode
		{
			get { return orderMode; }
		}

		public int SubModeOrder
		{
			get { return subOrderMode; }
		}

		public bool IsOrdered
		{
			get { return isOrdered; }
		}

		#endregion

		#region IComparable Implementation

		public int CompareTo(object obj)
		{
			Assembly A = obj as Assembly;

			if ( A == null )
			{
				throw new ArgumentException( "Could not compare objects. Invalid object type or object null." );
			}

			// If they have the same order mode, we must compare the 
			// submode order to decide which should come first.
			if ( this.OrderMode == A.OrderMode )
			{
				if ( this.SubModeOrder == A.SubModeOrder )
				{
					return 0;
				}
				else if ( this.SubModeOrder < A.SubModeOrder )
				{
					return -1;
				}

				return 1;
			}

			// If we are here, ordermodes are not equal and
			// this class's order mode is first (so A's isn't first).
			// This class should come first.
			if ( this.OrderMode == TestOrderModes.First )
			{
				return -1;
			}

			// If we are here, ordermodes are not equal and
			// this class's order mode is last (so A's isn't last).
			// m should come first.
			if ( this.OrderMode == TestOrderModes.Last )
			{
				return 1;
			}

			// This class's order mode isn't first or last
			// so we must determine whether A's order mode
			// is first or last (those are the only possiblities
			// left.
			if ( A.OrderMode == TestOrderModes.First )
			{
				return 1;
			}

			if ( A.OrderMode == TestOrderModes.Last )
			{
				return -1;
			}

			return 0;
		}

		#endregion

		#region IAssembly Members


		IClass[] IAssembly.Classes
		{
			get { return Classes; }
		}

		#endregion
	}
}